/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @author Alexei Y. Zakharov
 */

package org.apache.harmony.jndi.provider.dns;

import java.util.StringTokenizer;

import org.apache.harmony.jndi.internal.nls.Messages;

/**
 * Represents domain protocol Resource Record
 * 
 * @see RFC 1035
 */
public class ResourceRecord {

	/**
	 * Parses given sequence of bytes and constructs a resource record from it.
	 * 
	 * @param mesBytes
	 *            the byte array that should be parsed
	 * @param startIdx
	 *            an index of <code>mesBytes</code> array to start the parsing
	 *            at
	 * @param resultRR
	 *            an object the result of the operation will be stored into
	 * @return updated index of <code>mesBytes</code> array
	 * @throws DomainProtocolException
	 *             if something went wrong
	 * @throws ArrayIndexOutOfBoundsException
	 *             if the array border unpredictably encountered
	 */
	public static int parseRecord(byte[] mesBytes, int startIdx,
			ResourceRecord resultRR) throws DomainProtocolException {
		int idx = startIdx;
		final StringBuffer nameSB = new StringBuffer();
		int rrType;
		int rdLen;
		Object rDat = null;

		if (resultRR == null) {
			// jndi.3F=Given resultRR is null
			throw new NullPointerException(Messages.getString("jndi.3F")); //$NON-NLS-1$
		}
		// NAME
		idx = ProviderMgr.parseName(mesBytes, idx, nameSB);
		resultRR.setName(ProviderMgr.normalizeName(nameSB.toString()));
		// TYPE
		rrType = ProviderMgr.parse16Int(mesBytes, idx);
		resultRR.setRRType(rrType);
		idx += 2;
		// CLASS
		resultRR.setRRClass(ProviderMgr.parse16Int(mesBytes, idx));
		idx += 2;
		// TTL
		resultRR.setTtl(ProviderMgr.parse32Int(mesBytes, idx));
		idx += 4;
		// RDLENGTH
		rdLen = ProviderMgr.parse16Int(mesBytes, idx);
		idx += 2;
		// RDATA
		if (rrType == ProviderConstants.NS_TYPE
				|| rrType == ProviderConstants.CNAME_TYPE
				|| rrType == ProviderConstants.PTR_TYPE) {
			// let's parse the domain name
			final StringBuffer name = new StringBuffer();

			idx = ProviderMgr.parseName(mesBytes, idx, name);
			rDat = ProviderMgr.normalizeName(name.toString());
		} else if (rrType == ProviderConstants.A_TYPE) {
			// let's parse the 32 bit Internet address
			final byte tmpArr[] = new byte[4];

			for (int i = 0; i < 4; i++) {
				tmpArr[i] = mesBytes[idx + i];
			}
			rDat = ProviderMgr.getIpStr(tmpArr);
			idx += 4;
		} else if (rrType == ProviderConstants.MX_TYPE) {
			// 16 bit integer (preference) followed by domain name
			int preference;
			final StringBuffer name = new StringBuffer();

			preference = ProviderMgr.parse16Int(mesBytes, idx);
			idx += 2;
			idx = ProviderMgr.parseName(mesBytes, idx, name);
			rDat = "" + preference + " " + //$NON-NLS-1$ //$NON-NLS-2$
					ProviderMgr.normalizeName(name.toString());
		} else if (rrType == ProviderConstants.SOA_TYPE) {
			final StringBuffer mName = new StringBuffer();
			final StringBuffer rName = new StringBuffer();
			long serial;
			long refresh;
			long retry;
			long expire;
			long minimum;

			idx = ProviderMgr.parseName(mesBytes, idx, mName);
			idx = ProviderMgr.parseName(mesBytes, idx, rName);
			serial = ProviderMgr.parse32Int(mesBytes, idx);
			idx += 4;
			refresh = ProviderMgr.parse32Int(mesBytes, idx);
			idx += 4;
			retry = ProviderMgr.parse32Int(mesBytes, idx);
			idx += 4;
			expire = ProviderMgr.parse32Int(mesBytes, idx);
			idx += 4;
			minimum = ProviderMgr.parse32Int(mesBytes, idx);
			idx += 4;
			rDat = ProviderMgr.normalizeName(mName.toString()) + " " + //$NON-NLS-1$
					ProviderMgr.normalizeName(rName.toString()) + " " + //$NON-NLS-1$
					serial + " " + refresh + " " + retry + " " + expire + " " + //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
					minimum;
		} else if (rrType == ProviderConstants.TXT_TYPE) {
			final StringBuilder sbuf = new StringBuilder();
			final int idx0 = idx;

			while (true) {
				final int len11 = ProviderMgr.parse8Int(mesBytes, idx++);

				if (idx - idx0 + len11 > rdLen) {
					idx--;
					break;
				}
				if (sbuf.length() > 0) {
					sbuf.append(' ');
				}
				sbuf.append(new String(mesBytes, idx, len11));
				idx += len11;
			}
			rDat = sbuf.toString();
		} else if (rrType == ProviderConstants.HINFO_TYPE) {
			// two character strings with preceding length octets
			final StringBuffer res = new StringBuffer();

			idx = ProviderMgr.parseCharString(mesBytes, idx, res);
			res.append(" "); //$NON-NLS-1$
			idx = ProviderMgr.parseCharString(mesBytes, idx, res);
			rDat = res.toString();
		} else if (rrType == ProviderConstants.SRV_TYPE) {
			int priority;
			int weight;
			int port;
			final StringBuffer name = new StringBuffer();

			priority = ProviderMgr.parse16Int(mesBytes, idx);
			idx += 2;
			weight = ProviderMgr.parse16Int(mesBytes, idx);
			idx += 2;
			port = ProviderMgr.parse16Int(mesBytes, idx);
			idx += 2;
			idx = ProviderMgr.parseName(mesBytes, idx, name);
			rDat = "" + priority + " " + weight + " " + port + " " + //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
					ProviderMgr.normalizeName(name.toString());
		}
		// TODO add more Resource Record types here
		else {
			// copy bytes since the retrieved bytes
			// could contain unknown binary data
			rDat = new byte[rdLen];
			for (int i = 0; i < rdLen; i++) {
				((byte[]) rDat)[i] = mesBytes[idx++];
			}
		}
		resultRR.setRData(rDat);
		return idx;
	}

	/** a domain name */
	private String name;

	/** resource record type */
	private int rrType;

	/** resource record class */
	private int rrClass;

	/** time to live */
	private long ttl;

	/** resource data length */
	// private int rdLength;
	/** resource data itself */
	private Object rData;

	/** empty constructor */
	public ResourceRecord() {
	}

	/**
	 * Constructs new ResourceRecord object from given values.
	 * 
	 * @param name
	 *            a domain name
	 * @param rrType
	 *            resource record type
	 * @param rrClass
	 *            resource record class
	 * @param ttl
	 *            time to live
	 * @param rdLength
	 *            resource data length
	 * @param rData
	 *            resource data itself
	 */
	public ResourceRecord(String name, int rrType, int rrClass, long ttl,
	/* int rdLength, */
	Object rData) {
		this.name = name;
		this.rrType = rrType;
		this.rrClass = rrClass;
		this.ttl = ttl;
		// this.rdLength = rdLength;
		this.rData = rData;
	}

	/**
	 * @return Returns the name.
	 */
	public String getName() {
		return name;
	}

	/**
	 * @return Returns the rData.
	 */
	public Object getRData() {
		return rData;
	}

	// getters and setters

	/**
	 * @return Returns the rdLength.
	 */
	// public int getRDLength() {
	// return rdLength;
	// }
	/**
	 * @param rdLength
	 *            The rdLength to set.
	 */
	// public void setRDLength(int rdLength) {
	// this.rdLength = rdLength;
	// }
	/**
	 * @return Returns the rrClass.
	 */
	public int getRRClass() {
		return rrClass;
	}

	/**
	 * @return Returns the rrType.
	 */
	public int getRRType() {
		return rrType;
	}

	/**
	 * @return Returns the TTL.
	 */
	public long getTtl() {
		return ttl;
	}

	/**
	 * @param name
	 *            The name to set.
	 */
	public void setName(String name) {
		this.name = name;
	}

	/**
	 * @param data
	 *            The rData to set.
	 */
	public void setRData(Object data) {
		rData = data;
	}

	/**
	 * @param rrClass
	 *            The rrClass to set.
	 */
	public void setRRClass(int rrClass) {
		this.rrClass = rrClass;
	}

	/**
	 * @param rrType
	 *            The rrType to set.
	 */
	public void setRRType(int rrType) {
		this.rrType = rrType;
	}

	/**
	 * @param ttl
	 *            The TTL to set.
	 */
	public void setTtl(long ttl) {
		this.ttl = ttl;
	}

	@Override
	public String toString() {
		final StringBuilder sb = new StringBuilder();
		sb.append(name);
		sb.append(" "); //$NON-NLS-1$
		sb.append(ProviderConstants.rrTypeNames[rrType]);
		sb.append(" "); //$NON-NLS-1$
		sb.append(rrClass);
		sb.append(" "); //$NON-NLS-1$
		sb.append("TTL=" + ttl); //$NON-NLS-1$
		sb.append(" "); //$NON-NLS-1$
		sb.append(rData.toString());
		return sb.toString();
	}

	/**
	 * Creates the sequence of bytes that represents the current resource
	 * record.
	 * 
	 * @param buffer
	 *            the buffer to write the bytes into
	 * @param startIdx
	 *            starting index
	 * @return updated index
	 * @throws DomainProtocolException
	 *             if something went wrong
	 * @throws ArrayIndexOutOfBoundsException
	 *             if the buffer border unpredictably encountered
	 */
	public int writeBytes(byte[] buffer, int startIdx)
			throws DomainProtocolException {
		int idx = startIdx;

		// basic checking
		if (buffer == null) {
			// jndi.32=buffer is null
			throw new DomainProtocolException(Messages.getString("jndi.32")); //$NON-NLS-1$
		}
		// NAME
		idx = ProviderMgr.writeName(name, buffer, idx);
		// TYPE
		idx = ProviderMgr.write16Int(rrType, buffer, idx);
		// CLASS
		idx = ProviderMgr.write16Int(rrClass, buffer, idx);
		// TTL
		idx = ProviderMgr.write32Int(ttl, buffer, idx);
		// RDLENGTH & RDATA
		if (rrType == ProviderConstants.NS_TYPE
				|| rrType == ProviderConstants.CNAME_TYPE
				|| rrType == ProviderConstants.PTR_TYPE) {
			final int idx0 = idx;

			idx += 2;
			// RDATA
			idx = ProviderMgr.writeName((String) rData, buffer, idx);
			// RDLENGTH
			ProviderMgr.write16Int(idx - 2 - idx0, buffer, idx0);
		} else if (rrType == ProviderConstants.A_TYPE) {
			final byte[] ipBytes = ProviderMgr.parseIpStr((String) rData);

			// RDLENGTH
			idx = ProviderMgr.write16Int(ipBytes.length, buffer, idx);
			for (final byte element : ipBytes) {
				buffer[idx++] = element;
			}
		} else if (rrType == ProviderConstants.SOA_TYPE) {
			final StringTokenizer st = new StringTokenizer((String) rData, " "); //$NON-NLS-1$
			String token;
			final int idx0 = idx; // saving RDLENGTH position

			if (st.countTokens() != 7) {
				// jndi.35=Invalid number of fields while parsing SOA record
				throw new DomainProtocolException(Messages.getString("jndi.35")); //$NON-NLS-1$
			}
			idx += 2; // skip RDLENGTH for now
			// RDATA
			// MNAME
			token = st.nextToken();
			idx = ProviderMgr.writeName(token, buffer, idx);
			// RNAME
			token = st.nextToken();
			idx = ProviderMgr.writeName(token, buffer, idx);
			// SERIAL
			// REFRESH
			// RETRY
			// EXPIRE
			// MINIMUM
			try {
				for (int i = 0; i < 5; i++) {
					token = st.nextToken();
					idx = ProviderMgr.write32Int(Long.parseLong(token), buffer,
							idx);
				}
			} catch (final NumberFormatException e) {
				// jndi.36=Error while parsing SOA record
				throw new DomainProtocolException(
						Messages.getString("jndi.36"), e); //$NON-NLS-1$
			}
			// RDLENGTH
			ProviderMgr.write16Int(idx - 2 - idx0, buffer, idx0);
		} else if (rrType == ProviderConstants.MX_TYPE) {
			final StringTokenizer st = new StringTokenizer((String) rData, " "); //$NON-NLS-1$
			String token;
			final int idx0 = idx; // saving RDLENGTH position

			if (st.countTokens() != 2) {
				// jndi.37=Invalid number of fields while parsing MX record
				throw new DomainProtocolException(Messages.getString("jndi.37")); //$NON-NLS-1$
			}
			idx += 2; // skip RDLENGTH for now
			// PREFERENCE
			token = st.nextToken();
			try {
				ProviderMgr.write16Int(Integer.parseInt(token), buffer, idx);
			} catch (final NumberFormatException e) {
				// jndi.38=Error while parsing MX record
				throw new DomainProtocolException(
						Messages.getString("jndi.38"), e); //$NON-NLS-1$
			}
			// EXCHANGE
			token = st.nextToken();
			idx = ProviderMgr.writeName(token, buffer, idx);
			// RDLENGTH
			ProviderMgr.write16Int(idx - 2 - idx0, buffer, idx0);
		} else if (rrType == ProviderConstants.HINFO_TYPE) {
			final StringTokenizer st = new StringTokenizer((String) rData, " "); //$NON-NLS-1$
			String token;
			final int idx0 = idx; // saving RDLENGTH position

			if (st.countTokens() != 2) {
				// jndi.39=Invalid number of fields while parsing HINFO record
				throw new DomainProtocolException(Messages.getString("jndi.39")); //$NON-NLS-1$
			}
			idx += 2; // skip RDLENGTH for now
			// CPU
			// OS
			for (int i = 0; i < 2; i++) {
				token = st.nextToken();
				idx = ProviderMgr.writeCharString(token, buffer, idx);
			}
			// RDLENGTH
			ProviderMgr.write16Int(idx - 2 - idx0, buffer, idx0);
		} else if (rrType == ProviderConstants.TXT_TYPE) {
			// character string with preceding length octet
			final int idx0 = idx;
			final StringTokenizer st = new StringTokenizer((String) rData, " "); //$NON-NLS-1$

			idx += 2;
			// RDATA
			while (st.hasMoreTokens()) {
				final String token = st.nextToken();

				if (token.getBytes().length > 255) {
					// jndi.3A=The length of character string exceed 255 octets
					throw new DomainProtocolException(
							Messages.getString("jndi.3A")); //$NON-NLS-1$
				}
				idx = ProviderMgr.writeCharString(token, buffer, idx);
			}
			if (idx - 2 - idx0 > 65535) {
				// jndi.3B=Length of TXT field exceed 65535
				throw new DomainProtocolException(Messages.getString("jndi.3B")); //$NON-NLS-1$
			}
			// RDLENGTH
			ProviderMgr.write16Int(idx - 2 - idx0, buffer, idx0);
		} else if (rrType == ProviderConstants.SRV_TYPE) {
			final StringTokenizer st = new StringTokenizer((String) rData, " "); //$NON-NLS-1$
			String token;
			final int idx0 = idx; // saving RDLENGTH position

			idx += 2;
			if (st.countTokens() != 4) {
				// jndi.3C=Invalid number of fields while parsing SRV record
				throw new DomainProtocolException(Messages.getString("jndi.3C")); //$NON-NLS-1$
			}
			// RDATA

			// PRIORITY
			// WEIGHT
			// PORT
			try {
				for (int i = 0; i < 3; i++) {
					token = st.nextToken();
					idx = ProviderMgr.write16Int(Integer.parseInt(token),
							buffer, idx);
				}
			} catch (final NumberFormatException e) {
				// jndi.3D=Error while parsing SRV record
				throw new DomainProtocolException(
						Messages.getString("jndi.3D"), e); //$NON-NLS-1$
			}
			// TARGET
			token = st.nextToken();
			idx = ProviderMgr.writeName(token, buffer, idx);
			// RDLENGTH
			ProviderMgr.write16Int(idx - 2 - idx0, buffer, idx0);
		}
		// TODO add more Resource Record types here
		else {
			byte[] bytes;

			if (!(rData instanceof byte[])) {
				// jndi.3E=RDATA for unknown record type {0} should have value
				// of byte[] type
				throw new DomainProtocolException(Messages.getString(
						"jndi.3E", rrType)); //$NON-NLS-1$
			}
			bytes = (byte[]) rData;
			// RDLENGTH
			idx = ProviderMgr.write16Int(bytes.length, buffer, idx);
			for (final byte element : bytes) {
				buffer[idx++] = element;
			}
		}
		return idx;
	}
}
